class Promise {
    constructor(excutor) {
        let that = this;
        that.value = undefined;
        that.reason = undefined;
        that.status = Promise._getStatus().PENDING;
        that.onResolvedCallbacks = [];
        that.onRejectedCallbacks = [];
        let resolve = (value) => {
            if (that.status === Promise._getStatus().PENDING) {
                that.status = Promise._getStatus().FULLFILLED;
                that.value = value;
                that.onResolvedCallbacks.forEach(fulfiled => fulfiled());
            }
        }
        let reject = (reason) => {
            if (that.status === Promise._getStatus().PENDING) {
                that.status = Promise._getStatus().REJECTED;
                that.reason = reason;
                that.onRejectedCallbacks.forEach(reject => reject());
            }
        }

        try {
            excutor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    then(onFulfiled, onRejected) {
        onFulfiled = typeof onFulfiled === 'function' ? onFulfiled : data => data;
        onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};

        let that = this;
        let promise2 = new Promise((resolve, reject) => {
            if(that.status === Promise._getStatus().FULLFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfiled(that.value)
                        Promise._resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            }

            if(that.status === Promise._getStatus().REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(that.reason);
                        Promise._resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            }

            if(that.status === Promise._getStatus().PENDING) {
                that.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfiled(that.value)
                            Promise._resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    },0)
                });
                that.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(that.reason);
                            Promise._resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    },0)
                });
            }
        })
        return promise2;
    }

    static _getStatus() {
        return {
            FULLFILLED: 'FULLFILLED',
            REJECTED: 'REJECTED',
            PENDING: 'PENDING'
        }
    }

    static _resolvePromise(promise2, x, resolve, reject) {
        if(promise2 === x) {
            return reject(new Error("Circle Reference!"));
        }

        let called;
        if((typeof x === 'object' && x !== null) || typeof x === 'function') {
            try {
                let then = x.then;
                if(typeof then === 'function') {
                    then.call(x, y => {
                        if (called) return;
                        called = !called;
                        Promise._resolvePromise(promise2, y, resolve, reject);
                    }, r => {
                        if (called) return;
                        called = !called;
                        reject(r);
                    })
                } else {
                    resolve(x);
                }
            } catch (e) {
                if (called) return;
                called = !called;
                reject(e);
            }
        } else {
            resolve(x);
        }
    }

    static resolve(value) {
        return new Promise(resolve => {
            resolve(value);
        });
    }

    static rejected(reason) {
        return new Promise(reject => reject(reason));
    }

    static deferred(){
        let deffer = {};
        deffer.promise = new Promise((resolve, reject) => {
            deffer.resolve = resolve;
            deffer.reject = reject;
        })

        return deffer;
    }

    static defer() {
        return Promise.deferred();
    }

    static finally(callback) {
        return this.then(data => {
            callback();
            return data;
        }, error => {
            callback();
            throw error;
        })
    }

    static catch(callback) {
        return this.then(null, callback)
    }

    static all(promises) {
        return new Promise((resolve, reject) => {
            let arr = []; let i = 0
            function processData(value, index) {
                arr[index] = value;
                if (++i === promises.length) { resolve(arr); }
            }

            for(let i = 0; i < promises - 1; i++) {
                promises[i].then(
                    value => {processData(value, i)},
                    reject)
            }
        })
    }

    static race(promises) {
        return new Promise((resolve, reject) => {
            for(let i = 0; i < promises - 1; i++) {
                promises[i].then(value => resolve(value), reject)
            }
        })
    }
}

module.exports = Promise;